Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#47037 - Create Remote Dialer Proxy #90

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

#47037 - Create Remote Dialer Proxy #90

wants to merge 12 commits into from

Conversation

gehrkefc
Copy link

maxsokolovsky and others added 3 commits November 8, 2024 12:12
* Add a port-forward type

* Add support for automatic reconnects when pods are terminated

* List clients

* Add a Dockerfile for the proxy

* Add proxy

* Update go.mod

* Update package name

* Add config parsing

* Immediately close connection when there are no clients
@gehrkefc gehrkefc changed the title added proxy client and server working impl, also some examples #47037 - Create Remote Dialer Proxy Jan 28, 2025
@gehrkefc gehrkefc marked this pull request as ready for review January 28, 2025 20:02
@gehrkefc gehrkefc requested a review from a team as a code owner January 28, 2025 20:02
@tomleb tomleb requested a review from joshmeranda January 28, 2025 21:52
Copy link

@joshmeranda joshmeranda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM from an integration with imperative api perspective, but have some smaller comments

cmd/proxy/deployment.yaml Outdated Show resolved Hide resolved
cmd/proxy/deployment.yaml Outdated Show resolved Hide resolved
cmd/proxy/main.go Outdated Show resolved Hide resolved
cmd/proxy/main.go Outdated Show resolved Hide resolved
examples/fakek8s/Dockerfile Show resolved Hide resolved
forward/forward.go Show resolved Hide resolved

if failed {
return err
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably get rid of failed and use err in the same way

var readyErr error

...
                if err := r.runForwarder(ctx, r.readyCh, r.stopCh, r.Ports); err != nil {
if errors.Is(err, portforward.ErrLostConnectionToPod) {
                    logrus.Errorf("Lost connection to pod (no automatic retry in this refactor): %v", err)
                } else {
                    logrus.Errorf("Non-restartable error: %v", err)
                    readyErr = err
                    r.readyCh <- struct{}{}
                    return
					}
                }
...

if readyErr != nil {
return err
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, just changed it. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will trigger a race condition with the race detector because we have:

G1: write readyErr (init)
G2: write readyErr
G1: read readyErr

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unresolving this due to my comment above^ Do you disagree @joshmeranda / @gehrkefc

forward/forward.go Outdated Show resolved Hide resolved
secret.go Outdated Show resolved Hide resolved
@tomleb
Copy link
Contributor

tomleb commented Jan 29, 2025

@gehrkefc Currently this PR points to imperative-api branch, but we should target the main branch of remotedialer. The imperative-api branch was a quick branch without review since Max was leaving. So let's review the entirety of the changes instead.

@gehrkefc gehrkefc changed the base branch from imperative-api to main January 29, 2025 15:18
joshmeranda
joshmeranda previously approved these changes Jan 29, 2025
Copy link
Contributor

@tomleb tomleb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First set of comments, reviewed mostly the core functionality.

forward/forward.go Show resolved Hide resolved

if failed {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will trigger a race condition with the race detector because we have:

G1: write readyErr (init)
G2: write readyErr
G1: read readyErr

forward/forward.go Outdated Show resolved Hide resolved
logrus.Infoln("Goroutine stopped.")
return
default:
err := r.runForwarder(ctx, r.readyCh, r.stopCh, r.Ports)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we replace r.stopCh with ctx.Done() ?

Copy link
Author

@gehrkefc gehrkefc Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Replaced it! Thanks!

proxy/config.go Outdated Show resolved Hide resolved
proxy/config.go Outdated Show resolved Hide resolved
proxy/server.go Outdated Show resolved Hide resolved
proxyclient/client.go Outdated Show resolved Hide resolved
Comment on lines 99 to 112
secret, err := secretController.Get(namespace, certSecretName, metav1.GetOptions{})
if err != nil {
return nil, err
}

crtData, exists := secret.Data["tls.crt"]
if !exists {
return nil, fmt.Errorf("secret %s/%s missing tls.crt field", namespace, certSecretName)
}

rootCAs := x509.NewCertPool()
if ok := rootCAs.AppendCertsFromPEM(crtData); !ok {
return nil, fmt.Errorf("failed to parse tls.crt from secret into a CA pool")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The certs will be rotated by dynamiclistener before they expire (or at least that's the intention) so we should react to changes for this. We can add that functionality in the next PR, same as we're doing with @joshmeranda 's PR. We do need that though as part of the GH issue.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented this. Please check it. I tested it modifying a local copy of dynamic listener to generate a new cert at each 20s. Please check the image below to see both working.

In the lower terminal you'll see the server regenerating the secret and updating it. Then in the upper terminal you'll see the client receiving the event with the new cert.

image

@gehrkefc
Copy link
Author

First set of comments, reviewed mostly the core functionality.

I've made another change in the server in the runProxyListener function because of a high cpu usage.

Comment on lines +99 to +102
core, err := core.NewFactoryFromConfigWithOptions(restConfig, nil)
if err != nil {
return fmt.Errorf("build secret controller failed: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically try not to create too many controller factories in Rancher to avoid duplicating data (memory). Since this client is going to run in Rancher, and Rancher already has controllers / caches for secrets, let's reuse those. To do this, you can make it so that New() takes a SecretController, and when we integrate with Rancher, we'll pass the controller.

Comment on lines +105 to +107
secretController.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
UpdateFunc: func(oldSecret, newSecret interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
secretController.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
UpdateFunc: func(oldSecret, newSecret interface{}) {
secretController.OnChange(ctx, "remotedialer-proxy", func(_ string, secret *corev1.Secret) (*corev1.Secret, error) {

OnChange is the most typical way of adding a controller. Informer().AddEventHandler() is more low-level (and I haven't really seen it used, but it might because I wasn't looking at places that use it)

UpdateFunc: func(oldSecret, newSecret interface{}) {
updatedSecretCert, ok := newSecret.(*corev1.Secret)
if ok {
if updatedSecretCert.Name == c.certSecretName {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both namespace and name should be checked.

Comment on lines +131 to +133
if err := core.Start(ctx, 1); err != nil {
return fmt.Errorf("secret controller factory start failed: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed since we're just going to pass in the controller, the controller will already be started in Rancher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants